#include <os/driver.h>
#include <os/memspace.h>
#include <os/mutexlock.h>
#include <os/fifoio.h>
#include <os/schedule.h>
#include <os/initcall.h>
#include <sys/res.h>
#include <sys/ioctl.h>
#include <lib/stdio.h>
#include <driver/tty.h>

static int TTYProcess(device_extension_t *extension, int keycode);
static int TTYProcessSwitch(device_extension_t *extension, int keycode);
static uint8_t _KeycodeSwitch(int keycode);
static int TTYFilterKeycodeDown(device_extension_t *extension, int keycode);
static int TTYFilterKeycodeUp(device_extension_t *extension, int keycode);
static void TTYThread(void *);
static int _TTYWrite(device_extension_t *extension, char *buff, int len);

#define ISPRINT(c) (c >= 32 && c <= 127)


#define TTY_DEBUG

static iostatus_t TTYEntry(driver_object_t *driver)
{
    int i;
    char devname[DEVICE_NAME_LEN+1];
    iostatus_t status;
    device_object_t *devobj;
    device_extension_t *extension;
    device_public_t *pub;
    device_handle_t keyboard;
    uint8_t *buff;

    pub = KMemAlloc(sizeof(device_public_t));
    if (!pub)
    {
        return IO_FAILED;
    }
    pub->current_device = -1;
    pub->count = 0;
    // open keyboard device
    keyboard = DeviceOpen(KEYBOARD_DEVICE_NAME, 0);
    if (keyboard < 0)
    {
        KPrint(PRINT_ERR "TTYEntry: open keyboard failed!\n");
        KMemFree(pub);
        return IO_FAILED;
    }

    // init all tty device
    for (i = 0; i < TTY_DEVICE_NUM; i++)
    {
        // device name is DEVICE_NAME+i
        memset(devname, 0, DEVICE_NAME_LEN);
        sprintf(devname, "%s%d", DEVICE_NAME, i);
        // create device
        status = IoCreateDevice(driver, sizeof(device_extension_t), devname, DEVICE_TYPE_VIRTUAL_CHAR, &devobj);
        if (status != IO_SUCCESS)
        {
            KPrint(PRINT_ERR "TTYEnter: create device %s failed.\n", devname);
            DeviceClose(keyboard);
            KMemFree(pub);
            return status;
        }
        // other
        devobj->flags = 0;
        extension = devobj->device_extension;
        extension->device_object = devobj;
        extension->device_id = i;
        extension->process_group_id = -1;
        extension->keyboard = keyboard;
        extension->console = -1;
        extension->modify_ctl = 0;
        extension->modify_alt = 0;
        extension->flags = TTY_FLAGS_ECHO;
        extension->public  = pub;

        buff = KMemAlloc(DEVICE_FIFO_BUFF_LEN);
        if (!buff)
        {
            status = IO_FAILED;
            KPrint(PRINT_ERR "alloc buffer failed\n");
            KMemFree(extension);
            KMemFree(pub);
            DeviceClose(keyboard);
            IoDeleteDevice(devobj);
            return status;
        }
        // init device fifo buffer
        FifoIoInit(&extension->fifoio, buff, DEVICE_FIFO_BUFF_LEN);
       
        // set current device to first tty device
        if (pub->current_device == -1)
        {
            pub->current_device = i;
        }
        pub->extension[i] = (device_extension_t *)extension;
        pub->detach_keyboard = 0; // keyboard no detach
    }
    KPrint("[tty] create tty device ok!\n");
    return status;
}

static iostatus_t TTYExit(driver_object_t *driver)
{
    device_object_t *devobj;
    list_traversal_all_owner_to_next(devobj, &driver->device_list, list)
    {
        // del device object
        IoDeleteDevice(devobj);
    }
    // del driver from system
    DriverObjectDel(driver);
}

static iostatus_t TTYOpen(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension = device->device_extension;
    char devname[DEVICE_NAME_LEN+1];

    memset(devname, 0, DEVICE_NAME_LEN);
    sprintf(devname, "%s%d", CONSOLE_DEVICE_NAME, extension->device_id);
    extension->console = DeviceOpen(devname, 0);
    // open failed
    if (extension->console < 0)
    {
        KPrint("[tty] console open error\n");
        status = IO_FAILED;
    }
    else
    {
        // open success
        if (extension->process_group_id == -1)
        {
            extension->process_group_id = cur_task->processgroup_id;
        }
        

        // first open create kernel thread
        if (extension->public->count == 0)
        {
            TaskCreate("tty", TASK_PRIOR_LEVEL_HIGH, TTYThread, extension->public);
            extension->public->count++;
        }
    }
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t TTYClose(device_object_t *devobj, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension = devobj->device_extension;

    if (extension->console >= 0)
    {
        // close console
        if (DeviceClose(extension->console) < 0)
        {
            status = IO_FAILED;
        }
        else
        {
            // close console success
            // free extension
            extension->console = -1;
            extension->process_group_id = -1;
        }
    }
    else
    {
        status = IO_FAILED;
    }
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static void TTYThread(void *arg)
{
    device_public_t *pub = (device_public_t *)arg;
    device_extension_t *extension =NULL;
    input_event_t event;
    int bytes = 0;
    uint8_t ch = 0;


    while (1)
    {    
        extension= pub->extension[pub->current_device];

        memset(&event, 0, sizeof(event));
        // read keyboard event
        bytes = DeviceRead(extension->keyboard, &event, sizeof(event), 0);
        if (bytes > 0)
        {
            switch (event.type)
            {
                // key event
            case EVENT_KEY:
                // raw device
                if (pub->current_device == TTY_DEVICE_RAW)
                {
                    if (event.value > 0)
                    {
                        // key down
                        TTYFilterKeycodeDown(extension, event.code);
                        // only deal switch key
                        if (TTYProcessSwitch(extension, event.code) < 0)
                        {
                            // put to fifo io buffer
                            FifoIoPut(&extension->fifoio, event.code);
                            FifoIoPut(&extension->fifoio, 0);
                            //KPrint("tty %d fifo %x\n",extension->device_id,&extension->fifoio);
                        }
                    }
                    else
                    {
                        // key up
                        TTYFilterKeycodeUp(extension, event.code);
                        // put to fifo io  buff
                        FifoIoPut(&extension->fifoio, event.code);
                        FifoIoPut(&extension->fifoio, 1);
                    }
                }
                else
                {
                    if (event.value > 0)
                    {
                        // key down
                        if (TTYFilterKeycodeDown(extension, event.code) < 0)
                        {
                            ch = _KeycodeSwitch(event.code);
                            if (ch > 0)
                                event.code = ch;

                            // process system key
                            if (TTYProcess(extension, event.code) < 0)
                            {
                                FifoIoPut(&extension->fifoio, event.code);
                                if ((extension->flags & TTY_FLAGS_ECHO))
                                {
                                    DeviceWrite(extension->console, &event.code, 1, 0);
                                }
                            }
                        }
                    }
                    else
                    {
                        // key up
                        TTYFilterKeycodeUp(extension, event.code);
                    }
                }
                break;
            default:
                break;
            }
        }
    }
}

iostatus_t TTYRead(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    uint8_t *buff = ioreq->user_buff;
    size_t len = ioreq->parame.read.len;
    size_t read_count = 0;
    size_t fifo_len;
    uint8_t code;

    //KPrint("public->current %d device id %d fifo %x\n",extension->public->current_device,extension->device_id,&extension->fifoio);

    //  if assign no wait just check if had data
    if (extension->flags & TTY_FLAGS_NOWAIT)
    {
        fifo_len = FifoIoLen(&extension->fifoio);
        if (fifo_len <= 0)
        {
            status = IO_FAILED;
            ioreq->io_status.status = status;
            ioreq->io_status.info = -1;
            IoCompleteRequest(ioreq);
            return status;
        }
    }

    // if is current device
    while (extension->public->current_device != extension->device_id)
    {
        TaskYield();
    }

    // raw device or activity task
    if ((extension->public->current_device == TTY_DEVICE_RAW) || (extension->process_group_id == cur_task->processgroup_id))
    {
        while (len-- > 0)
        {
            code = FifoIoGet(&extension->fifoio);
            if (code > 0) // normal character
            {
                *buff++ = code;
                read_count++;
                continue;
            }
            // fifo io buffer eof
            break;
        }
        ioreq->io_status.info = read_count;
    }
    else
    {
        // force hardware exception
        KPrint("pid %d no process group %d!\n", cur_task->pid, extension->process_group_id);
        ExceptionForceSelf(EXC_CODE_TTIN);
    }
    ioreq->io_status.status = status;
    IoCompleteRequest(ioreq);
    return IO_SUCCESS;
}

iostatus_t TTYDevCtl(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    uint64_t *arg = (uint64_t *)ioreq->parame.devctl.arg;
    iostatus_t status = IO_SUCCESS;
    char *buff = NULL;
    int ret;
    uint32_t code = ioreq->parame.devctl.code;

    switch ((uint32_t)code)
    {
        // select tty
    case TTYIO_SELECT:
        TTYSetCurrent(extension, *arg);
        break;
        // set flags
    case TTYIO_SETFLAGS:
        extension->flags = *arg;
        break;
        // get flags
    case TTYIO_GETFLAGS:
        *arg = extension->flags;
        break;
        // set process group id
    case TTYIO_SETPGROUP:
        extension->process_group_id = *arg;
        break;
        // get process group id
    case TTYIO_GETPGROUP:
        *arg = extension->process_group_id;
        break;
        // is tty device
    case TTYIO_ISTTY:
        *arg = 1;
        break;
        // get device name
    case TTYIO_NAME:
    {
        buff = (uint8_t *)arg;
        strcpy(buff, device->name.text);
    }
    break;
    // other devctl code
    default:
        KPrint("send to console\n");
        // send devctl to console device
        ret = DeviceDevCtl(extension->console, ioreq->parame.devctl.code, (void *)ioreq->parame.devctl.arg);
        if (ret < 0)
            status = IO_FAILED;
        break;
    }
    // return io status
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static int _TTYWrite(device_extension_t *extension, char *buff, int len)
{
    char *p = buff;
    int num = 0;

    while (*p && len-- > 0)
    {
        // deal specific char
        switch (*p)
        {
        case '\003':
            ExceptionSendGroup(extension->process_group_id, EXC_CODE_INT);
        default:
            // write to console device
            if (DeviceWrite(extension->console, p, 1, 0) < 0)
                return -1;
            num++;
            break;
        }
        p++;
    }
    return num;
}

static iostatus_t TTYWrite(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    iostatus_t status = IO_SUCCESS;
    int len = _TTYWrite(extension, ioreq->user_buff, ioreq->parame.write.len);

    // io failed
    if (len < 0)
        status = IO_FAILED;

    ioreq->io_status.status = status;
    ioreq->io_status.info = len;

    IoCompleteRequest(ioreq);
    return status;
}

static int TTYProcess(device_extension_t *extension, int keycode)
{
    if (!TTYProcessSwitch(extension, keycode))
    {
        return 0;
    }
    if (extension->modify_ctl && (!extension->modify_alt))
    {
        switch (keycode)
        {
        case 'c':
        case 'C':
            KPrint("Send ctrl+C to pid %d\n",extension->process_group_id);
            // ctrl+c
            ExceptionSendGroup(extension->process_group_id, EXC_CODE_INT);
            break;
        default:
            return -1;
        }
        return 0;
    }
    return -1;
}

// swtich keypad key to master keyboard key
static uint8_t _KeycodeSwitch(int keycode)
{
    uint8_t key_value = 0;
    uint32_t i;

    for (i = 0; i < sizeof(_keycode_map_table); i += 2)
    {
        if (_keycode_map_table[i] == keycode)
        {
            key_value = _keycode_map_table[i + 1];
            return key_value;
        }
    }
    return 0;
}

static int TTYProcessSwitch(device_extension_t *extension, int keycode)
{
    int deviceid;

    // deal with ctrl+alt+keycode
    if (extension->modify_ctl && extension->modify_alt)
    {
        switch (keycode)
        {
        case KEY_F1:
        case KEY_F2:
        case KEY_F3:
        case KEY_F4:
        case KEY_F5:
        case KEY_F6:
        case KEY_F7:
        case KEY_F8:
        {
            deviceid = keycode - KEY_F1;
#ifdef TTY_DEBUG
            KPrint("[tty] switch to tty %d\n", deviceid);
#endif
            TTYSetCurrent(extension, deviceid);
        }
        break;
        default:
            return -1;
        }
        return 0;
    }
    return -1;
}

static int TTYFilterKeycodeDown(device_extension_t *extension, int keycode)
{
    switch (keycode)
    {
    case KEY_RCTRL:
    case KEY_LCTRL:
        extension->modify_ctl = 1;
        return 0;
    case KEY_LSHIFT:
    case KEY_RSHIFT:
    case KEY_NUMLOCK:
    case KEY_CAPSLOCK:
    case KEY_SCROLLOCK:
    case KEY_UP:
    case KEY_DOWN:
    case KEY_RIGHT:
    case KEY_LEFT:
        return -1;
    case KEY_LALT:
    case KEY_RALT:
        extension->modify_alt = 1;
        return 0;
    default:
        break;
    }
    return -1;
}

static int TTYFilterKeycodeUp(device_extension_t *extension, int keycode)
{
    switch (keycode)
    {
    case KEY_RCTRL:
    case KEY_LCTRL:
        extension->modify_ctl = 0;
        return 0;
    case KEY_LSHIFT:
    case KEY_RSHIFT:
    case KEY_NUMLOCK:
    case KEY_CAPSLOCK:
    case KEY_SCROLLOCK:
    case KEY_UP:
    case KEY_DOWN:
    case KEY_RIGHT:
    case KEY_LEFT:
        return -1;
    case KEY_LALT:
    case KEY_RALT:
        extension->modify_alt = 0;
        return 0;
    default:
        break;
    }
    return -1;
}

// tty driver function build
static iostatus_t TTYDriverFunc(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;

    // bind driver info
    driver->driver_enter = TTYEntry;
    driver->driver_exit = TTYExit;

    driver->dispatch_fun[IOREQ_OPEN] = TTYOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = TTYClose;
    driver->dispatch_fun[IOREQ_READ] = TTYRead;
    driver->dispatch_fun[IOREQ_WRITE] = TTYWrite;
    driver->dispatch_fun[IOREQ_DEVCTL] = TTYDevCtl;

    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);
#ifdef DEBUG_DRIVER
    KPrint(PRINT_DEBUG "TTYDriverFunc: driver name=%s create finished.\n", DEVICE_NAME);
#endif

    return status;
}

// tty driver entry
static void TTYDriverEntry(void)
{
    if (DriverObjectCreate(TTYDriverFunc) < 0)
    {
        KPrint(PRINT_ERR "[driver] %s create driver failed!\n", __func__);
    }
}

filter_initcall(TTYDriverEntry);
